const Joi = require('@hapi/joi')

const auxiliaryJoi = function (schemas, options = { strict: false }) {
    // 自定义校验选项
    // strict 自定义属性，默认不开启严格模式，会过滤掉那些未定义的参数项
    // 如果用户指定了 strict 的值为 true，则开启严格模式，此时不会过滤掉那些未定义的参数项
    if (!options.strict) {
        // allowUnknown 允许提交未定义的参数项
        // stripUnknown 过滤掉那些未定义的参数项
        options = { allowUnknown: true, stripUnknown: true, ...options }
    }

    // 从 options 配置对象中，删除自定义的 strict 属性
    delete options.strict

    // TODO: 用户指定了什么 schema，就应该校验什么样的数据
    return function (req, res, next) {
        ['body', 'query', 'params'].forEach(key => {
            // 如果当前循环的这一项 schema 没有提供，则不执行对应的校验
            if (!schemas[key]) return

            // 执行校验
            const schema = Joi.object(schemas[key])
            // options的配置项
            // abortEarly：当校验失败时，是否立即停止校验并返回错误信息。默认为 true。
            // allowUnknown：是否允许校验对象中存在未知属性。默认为 false。
            // convert：是否自动转换类型。默认为 true，即自动进行类型转换。
            // debug：是否开启调试模式。默认为 false。
            // escapeHtml：是否自动转义 HTML 实体字符。默认为 true。
            // language：指定错误信息的语言。默认为 'en'，即英文。
            // presence：当属性的值为 undefined 时，是否进行校验。默认为 'optional'，即可选。
            // stripUnknown：是否自动剔除未知属性。默认为 false。
            // skipFunctions：是否忽略函数类型的属性。默认为 false。
            // context：指定上下文对象，用于在自定义校验函数中访问其他属性和方法。
            // externals：指定外部引用对象，用于在自定义校验函数中访问外部对象和方法。
            // messages：指定自定义的错误信息，用于替换默认的错误信息。
            const { error, value } = schema.validate(req[key], options)

            if (error) {
                // 校验失败
                throw error
            } else {
                // 校验成功，把校验的结果重新赋值到 req 对应的 key 上
                req[key] = value
            }
        })

        // 校验通过
        next()
    }
}

module.exports = auxiliaryJoi